/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Animation.h"
#include <boost/fusion/include/for_each.hpp>

namespace liba {
namespace animate {

namespace
{
	struct Binder
	{
		Binder( Node * animation_root, bool recursion )
			: animation_root( animation_root )
			, recursion( recursion )
			, allOK( true )
		{
		}

		template< typename T >
		void operator() ( T & val ) const
		{
			allOK = allOK && val.bind( animation_root, recursion );
		}

		bool IsAllOK() const { return allOK; }

	private:
		Node *animation_root;
		bool recursion;
		mutable bool allOK;
	};

	struct PositionChanger
	{
		PositionChanger( const time_float & delta_time )
			: delta_time( delta_time )
		{
		}

		template< typename T >
		void operator() ( T & val ) const
		{
			val.change_position( delta_time );
		}

	private:
		time_float delta_time;
	};

	struct LengthSolver
	{
		LengthSolver()
			: length( std::numeric_limits<time_float>::min() )
		{
		}

		template< typename T >
		void operator() ( T & val ) const
		{
			length = std::max( length, val.get_length() );
		}

		time_float Length() const { return length; }

	private:
		mutable time_float length;
	};
}

Animation::Animation()
:	length( 0 ),
	time( 0 ),
	binded( false ),
	speed(1)
{}

Animation::~Animation()
{}

bool Animation::bind( Node * animation_root, bool recursion )
{
	if( binded )
	{
		change_time(0);
		return true;
	}
	binded = true;

	Binder binder( animation_root, recursion );
	boost::fusion::for_each( env, binder );

	change_time(0);

	return binder.IsAllOK();
}

void Animation::change_time(time_float time)
{
	this->time = time;

	PositionChanger changer( time );
	boost::fusion::for_each( env, changer );
}

time_float Animation::add_time(time_float delta_time)
{
	time_float remained = 0;
	time += static_cast<time_float>(delta_time * speed);
	if( time > length )
	{
		remained = time - length;
		time = length;
	}
	if( time < 0 )
	{
		remained = time;
		time = 0;
	}
	change_time( time );
	return remained;
}

int Animation::add_time_cycle(time_float delta_time)
{
	if( abs(length) < std::numeric_limits<double>::epsilon() )
		return 1;
	time += static_cast<time_float>(delta_time * speed);
	int resets = int( floor( time / length ) );
	time -= length * resets;

	change_time( time );
	return abs( resets );
}

void Animation::change_position(time_float position)
{
	change_time( length * position );
}

bool Animation::on_attribute(xml::Provider * prov, const std::string & name, const std::string & value )
{
	if(name == "Speed")
		return converter::convert(value, &speed);

	return false;
}

bool Animation::on_node( xml::Provider * prov, const std::string & name )
{
	if(name == "Animation")
		return prov->parse_object(this);

	if( name == "double" )
		return prov->parse_object( &boost::fusion::at_c<TYPES::DOUBLE>(env) );
	if( name == "MathVector3f" )
		return prov->parse_object( &boost::fusion::at_c<TYPES::VECTOR_3>(env) );
	if( name == "WString" )
		return prov->parse_object( &boost::fusion::at_c<TYPES::WSTRING>(env) );
	if( name == "FilesystemString" )
		return prov->parse_object( &boost::fusion::at_c<TYPES::FS_STRING>(env) );
	//if( name == "Commands" )
	//	return prov->parse_object( &boost::fusion::at_c<TYPES::COMMANDS>(env) );

	return false;
}

bool Animation::on_end_node( xml::Provider * prov )
{
	LengthSolver solver;
	boost::fusion::for_each( env, solver );
	length = solver.Length();

	return true;
}

void Animation::SetSpeed(float newSpeed)
{
	speed = newSpeed;
}

} // namespace animate
} // namespace liba

